Coverage Report

Created: 2025-05-07 21:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\tools.proto\tools.proto\compiler\src\gen\template\util.rs
Line
Count
Source
1
// Copyright (c) 2025, BlockProject 3D
2
//
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without modification,
6
// are permitted provided that the following conditions are met:
7
//
8
//     * Redistributions of source code must retain the above copyright notice,
9
//       this list of conditions and the following disclaimer.
10
//     * Redistributions in binary form must reproduce the above copyright notice,
11
//       this list of conditions and the following disclaimer in the documentation
12
//       and/or other materials provided with the distribution.
13
//     * Neither the name of BlockProject 3D nor the names of its contributors
14
//       may be used to endorse or promote products derived from this software
15
//       without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
use itertools::Itertools;
30
use regex::Regex;
31
use std::borrow::Cow;
32
use bp3d_util::string::BufTools;
33
34
#[allow(clippy::enum_variant_names)]
35
enum Convention {
36
    PascalCase,
37
    SnakeCase,
38
    ScreamingCase,
39
}
40
41
136
fn guess_case_convention(s: &str) -> Convention {
42
    //Assume all strings are Rust identifiers following standard Rust conventions:
43
    // snake_case, PascalCase and SCREAMING_CASE.
44
136
    let upper1 = (s.as_bytes()[0] >= b'A' && s.as_bytes()[0] <= b'Z') || 
s44
.as_bytes()[0] == b'_';
  Branch (44:19): [True: 136, False: 0]
  Branch (44:46): [True: 92, False: 44]
  Branch (44:19): [True: 0, False: 0]
  Branch (44:46): [True: 0, False: 0]
45
136
    let upper2 = (s.as_bytes()[s.as_bytes().len() - 1] >= b'A' && 
s94
.
as_bytes94
()[s.as_bytes().len() - 1] <= b'Z')
  Branch (45:19): [True: 94, False: 42]
  Branch (45:67): [True: 8, False: 86]
  Branch (45:19): [True: 0, False: 0]
  Branch (45:67): [True: 0, False: 0]
46
128
        || s.as_bytes()[s.as_bytes().len() - 1] == b'_';
47
136
    if upper1 && 
upper292
{
  Branch (47:8): [True: 92, False: 44]
  Branch (47:18): [True: 8, False: 84]
  Branch (47:8): [True: 0, False: 0]
  Branch (47:18): [True: 0, False: 0]
48
8
        Convention::ScreamingCase
49
128
    } else if upper1 {
  Branch (49:15): [True: 84, False: 44]
  Branch (49:15): [True: 0, False: 0]
50
84
        Convention::PascalCase
51
    } else {
52
44
        Convention::SnakeCase
53
    }
54
136
}
55
56
44
fn capitalize(value: &str) -> Cow<str> {
57
44
    match value.as_bytes().capitalise_ascii() {
58
0
        Cow::Borrowed(v) => String::from_utf8_lossy(v),
59
44
        Cow::Owned(v) => String::from(&*String::from_utf8_lossy(&*v)).into()
60
    }
61
44
}
62
63
0
fn decapitalize(value: &str) -> Cow<str> {
64
0
    match value.as_bytes().decapitalise_ascii() {
65
0
        Cow::Borrowed(v) => String::from_utf8_lossy(v),
66
0
        Cow::Owned(v) => String::from(&*String::from_utf8_lossy(&*v)).into()
67
    }
68
0
}
69
70
pub trait CaseConversion<'a> {
71
    fn to_pascal_case(self) -> Cow<'a, str>;
72
    fn to_snake_case(self) -> Cow<'a, str>;
73
    fn to_camel_case(self) -> Cow<'a, str>;
74
    fn to_screaming_case(self) -> Cow<'a, str>;
75
}
76
77
struct SnakeCase<'a>(&'a str);
78
struct PascalCase<'a>(&'a str);
79
struct ScreamingCase<'a>(&'a str);
80
81
impl<'a> CaseConversion<'a> for SnakeCase<'a> {
82
44
    fn to_pascal_case(self) -> Cow<'a, str> {
83
44
        self.0.split("_").map(capitalize).join("").into()
84
44
    }
85
86
0
    fn to_snake_case(self) -> Cow<'a, str> {
87
0
        self.0.into()
88
0
    }
89
90
0
    fn to_camel_case(self) -> Cow<'a, str> {
91
0
        self.0
92
0
            .split("_")
93
0
            .enumerate()
94
0
            .map(|(i, v)| if i != 0 { capitalize(v) } else { v.into() })
  Branch (94:30): [True: 0, False: 0]
  Branch (94:30): [True: 0, False: 0]
95
0
            .join("")
96
0
            .into()
97
0
    }
98
99
0
    fn to_screaming_case(self) -> Cow<'a, str> {
100
0
        self.0.split("_").map(|v| v.to_uppercase()).join("_").into()
101
0
    }
102
}
103
104
impl<'a> CaseConversion<'a> for PascalCase<'a> {
105
0
    fn to_pascal_case(self) -> Cow<'a, str> {
106
0
        self.0.into()
107
0
    }
108
109
28
    fn to_snake_case(self) -> Cow<'a, str> {
110
28
        let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap();
111
36
        let 
useless28
=
regex28
.
find_iter28
(
self.028
).
map28
(|v| v.as_str().to_lowercase()).
join28
(
"_"28
).
into28
();
112
28
        useless
113
28
    }
114
115
0
    fn to_camel_case(self) -> Cow<'a, str> {
116
0
        decapitalize(self.0)
117
0
    }
118
119
56
    fn to_screaming_case(self) -> Cow<'a, str> {
120
56
        let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap();
121
86
        let 
useless56
=
regex56
.
find_iter56
(
self.056
).
map56
(|v| v.as_str().to_uppercase()).
join56
(
"_"56
).
into56
();
122
56
        useless
123
56
    }
124
}
125
126
impl<'a> CaseConversion<'a> for ScreamingCase<'a> {
127
0
    fn to_pascal_case(self) -> Cow<'a, str> {
128
0
        self.0.split("_").map(|v| capitalize(&v.to_lowercase()).to_string()).join("").into()
129
0
    }
130
131
4
    fn to_snake_case(self) -> Cow<'a, str> {
132
4
        self.0.to_lowercase().into()
133
4
    }
134
135
0
    fn to_camel_case(self) -> Cow<'a, str> {
136
0
        self.0
137
0
            .split("_")
138
0
            .enumerate()
139
0
            .map(|(i, v)| {
140
0
                if i != 0 {
  Branch (140:20): [True: 0, False: 0]
  Branch (140:20): [True: 0, False: 0]
141
0
                    format!("{}", capitalize(&v.to_lowercase()))
142
                } else {
143
0
                    v.into()
144
                }
145
0
            })
146
0
            .join("")
147
0
            .into()
148
0
    }
149
150
4
    fn to_screaming_case(self) -> Cow<'a, str> {
151
4
        self.0.into()
152
4
    }
153
}
154
155
macro_rules! impl_case_conversion {
156
    ($s: expr, $func: ident) => {
157
        match guess_case_convention($s) {
158
            Convention::PascalCase => PascalCase($s).$func(),
159
            Convention::SnakeCase => SnakeCase($s).$func(),
160
            Convention::ScreamingCase => ScreamingCase($s).$func(),
161
        }
162
    };
163
}
164
165
impl<'a> CaseConversion<'a> for &'a str {
166
44
    fn to_pascal_case(self) -> Cow<'a, str> {
167
44
        impl_case_conversion!(
self0
, to_pascal_case)
168
44
    }
169
170
32
    fn to_snake_case(self) -> Cow<'a, str> {
171
32
        impl_case_conversion!(
self28
, to_snake_case)
172
32
    }
173
174
0
    fn to_camel_case(self) -> Cow<'a, str> {
175
0
        impl_case_conversion!(self, to_camel_case)
176
0
    }
177
178
60
    fn to_screaming_case(self) -> Cow<'a, str> {
179
60
        impl_case_conversion!(
self56
, to_screaming_case)
180
60
    }
181
}